package cn.yto.print.service;

import cn.hutool.json.JSONUtil;
import cn.yto.print.config.ThreadPoolManager;
import cn.yto.print.exception.BluetoothCommException;
import cn.yto.print.model.response.WeightResponse;
import cn.yto.print.socket.WeightSocketEndpoint;
import cn.yto.print.utils.WeightUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.bluetooth.DiscoveryAgent;
import javax.bluetooth.LocalDevice;
import javax.bluetooth.RemoteDevice;
import javax.microedition.io.Connector;
import javax.microedition.io.StreamConnection;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.ExecutorService;
import java.util.function.Consumer;

@Slf4j
@Service
public class BluetoothWeightService {

    private static final int TIMEOUT = 30 * 1000; // 连接超时时间（30秒）
    private static final int READ_BUFFER_SIZE = 1024;

    private static final String SPP_UUID = "1";

    private ExecutorService executor;
    private volatile boolean isRunning = false;

    private String bluetoothName;
    private StreamConnection streamConnection;

    @Autowired
    private WeightSocketEndpoint weightScoketEndpoint;

    /**
     * 读取蓝牙数据
     */
    public void readWeight(Consumer<String> onDataReceived) {
        startConnectBluetooth();
        if (null == streamConnection) {
            throw new BluetoothCommException("没有可用的蓝牙设备");
        }

        try {
            isRunning = true;
            startWeightReading(onDataReceived);
        } catch (Exception ex) {
            closeBluetooth();
            throw new RuntimeException(ex);
        }
    }

    /**
     * 连接蓝牙设备
     */
    private void startConnectBluetooth() {
        RemoteDevice[] remoteDevices = null;
        try {
            LocalDevice localDevice = LocalDevice.getLocalDevice();
            DiscoveryAgent discoveryAgent = localDevice.getDiscoveryAgent();
            remoteDevices = discoveryAgent.retrieveDevices(DiscoveryAgent.PREKNOWN);
            if (remoteDevices == null || remoteDevices.length == 0) {
                return;
            }
            log.info("发现 {} 个已配对蓝牙设备，开始尝试连接", remoteDevices.length);
        } catch (Exception e) {
            log.error("获取本地蓝牙配对设备异常：" + e.getMessage());
            return;
        }

        for (RemoteDevice device : remoteDevices) {
            try {
                bluetoothName = device.getFriendlyName(false);
                String connectionUrl = "btspp://" + device.getBluetoothAddress() + ":" + SPP_UUID;
                ConnectionThread connectionThread = new ConnectionThread(connectionUrl);
                connectionThread.start();
                connectionThread.join(TIMEOUT);
                // 处理连接结果
                if (connectionThread.isConnected()) {
                    streamConnection = connectionThread.getStreamConnection();
                    log.info("连接 {} 蓝牙设备成功, 开始读取设备数据 …", bluetoothName);
                    return;
                }
                if (connectionThread.getException() != null && !(connectionThread.getException() instanceof InterruptedException)) {
                    log.info("连接 {} 蓝牙设备失败：{}", bluetoothName, connectionThread.getException());
                } else {
                    log.info("连接 {} 蓝牙设备超时", bluetoothName);
                }
            } catch (Exception ex) {
                log.error("连接 {} 蓝牙设备发生异常", bluetoothName, ex);
            }
        }
    }

    private void startWeightReading(Consumer<String> onDataReceived) {
        if (null == executor || executor.isShutdown()) {
            executor = ThreadPoolManager.getExecutorService();
        }
        executor.execute(() -> {
            try (InputStream inputStream = streamConnection.openInputStream()) {
                byte[] buffer = new byte[READ_BUFFER_SIZE];
                int bytesRead;
                // 当isRunning为true且读取到数据时继续循环
                while (isRunning && (bytesRead = inputStream.read(buffer)) != -1) {
                    String receivedData = new String(buffer, 0, bytesRead, StandardCharsets.UTF_8);
                    double weight = WeightUtil.parseWeightData(receivedData);
                    WeightResponse weightResponse = new WeightResponse(1, weight);
                    weightScoketEndpoint.massMessage(JSONUtil.toJsonStr(weightResponse));
                    // 发送数据给小助手展示
                    onDataReceived.accept(String.format("%.2f", weight));
                }
            } catch (Exception ex) {
                log.error("读取蓝牙设备数据出现异常", ex);
            }
        });
    }

    public void closeBluetooth() {
        isRunning = false;
        if (null != streamConnection) {
            try {
                log.info("关闭 {} 蓝牙连接, 停止读取设备数据", bluetoothName);
                streamConnection.close();
            } catch (Exception e) {
                log.error("关闭蓝牙称重设备连接异常", e);
            }
            streamConnection = null;
        }
    }

    public String getBluetoothName() {
        return bluetoothName;
    }

    private static class ConnectionThread extends Thread {

        private final String connectionUrl;
        private boolean connected;
        private StreamConnection streamConnection;
        private Exception exception;

        public ConnectionThread(String connectionUrl) {
            this.connectionUrl = connectionUrl;
            this.connected = false;
            this.exception = null;
        }

        @Override
        public void run() {
            try {
                streamConnection = (StreamConnection) Connector.open(
                        connectionUrl,
                        Connector.READ_WRITE,
                        true
                );
                connected = true;
            } catch (Exception ex) {
                exception = ex;
            }
        }

        public boolean isConnected() {
            return connected;
        }

        public StreamConnection getStreamConnection() {
            return streamConnection;
        }

        public Exception getException() {
            return exception;
        }
    }

}
